package nio.demo;

import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;

/**
 * @Desc:   使用 FileChannel 读取数据到 Buffer 中的示例
 * @author: cww
 * @DateTime: 2020-02-02 20:26
 */

public class FileChannelDemo {
    public static void main(String[] args) throws Exception{
        RandomAccessFile accessFile = new RandomAccessFile("src\\maindemo\\resources\\file\\nio\\nio-data.txt", "rw");
        // 打开一个文件通道
        FileChannel inChannel = accessFile.getChannel();
        // 可以将文件加上一个排它锁
        FileLock lock = inChannel.lock();
        // 初始化一个 容量为 48 bytes 的 buffer 缓冲
        ByteBuffer buf = ByteBuffer.allocate(8);
        // 将通道中的文件内容读进 byteBuffer 中

        int bytesRead = inChannel.read(buf);
        while (bytesRead != -1) {
            System.out.println("Read 读取" + bytesRead);
            // 首先读取数据到 buffer, 一旦要读取数据，需要通过 flip() 方法将 buffer 从写模式切换到读模式，
            // 一种模式的翻转
            buf.flip();
            Charset latin1 = Charset.forName( "ISO-8859-1");
            CharsetDecoder decoder = latin1.newDecoder();
            while (buf.hasRemaining()) {
                // 编码/解码
//                CharBuffer cb = decoder.decode(buf);
                System.out.println(buf.get());
            }
            // 读完了所有数据，需要清空缓冲区，让它可以再次被写入
            // position 将被设回 0，limit 被设置成 capacity 的值，
            // 其实只是 Buffer 被清空了，但是 buffer 中的数据并未清除，
            // 只是这些标记告诉我们可以从哪里开始往 buffer 里写数据，
            // 如果 buffer 中有一些未读的数据，调用 clear 方法，数据将被遗忘，
            // 意味着不再有任何标记会告诉你哪些数据被度过哪些还没读
            buf.clear();
            System.out.println("write---------");
            buf.put(new byte[]{12,23});
            buf.flip();
            while (buf.hasRemaining()) {
                System.out.println(buf.get());
            }
            // rewind 将 position 设回 0，之后就可以重读 buffer 中所有的数据，limit 保持不变
            buf.rewind();

            // compact 将所有未读的数据拷贝到 buffer 起始处，然后将 position 设到最后一个未读元素正后方
            // limit 属性依然像 clear 方法一样，设置成 capacity，此时 buffer 准备好写数据了，
            // 但是不会覆盖未读的数据 (这里只是说明才放了这个方法)
//            buf.compact();
            // 通过调用Buffer.mark()方法，可以标记Buffer中的一个特定position。
            // 之后可以通过调用Buffer.reset()方法恢复到这个position
//            buf.mark();
//            buf.reset();
            // equals只是比较Buffer的一部分，不是每一个在它里面的元素都比较。实际上，它只比较Buffer中的剩余元素。
            // 当满足下列条件时，表示两个Buffer相等：
            // 有相同的类型（byte、char、int等）。
            // Buffer中剩余的byte、char等的个数相等。
            // Buffer中所有剩余的byte、char等都相同。
//            ByteBuffer buf1 = ByteBuffer.allocate(8);
//            buf.equals(buf1);
            // compareTo()方法比较两个Buffer的剩余元素(byte、char等)，
            // 如果满足下列条件，则认为一个Buffer“小于”另一个Buffer：
            // 第一个不相等的元素小于另一个Buffer中对应的元素 。
            // 所有元素都相等，但第一个Buffer比另一个先耗尽(第一个Buffer的元素个数比另一个少)。
//            buf.compareTo(buf1);
            bytesRead = inChannel.read(buf);
        }
        // 关闭通道
        accessFile.close();
    }

    /**
     * 往通道写数据
     */
    public void writeToChannel() throws Exception{
        RandomAccessFile accessFile = new RandomAccessFile("src\\maindemo\\resources\\file\\nio\\writeToChannel.txt", "rw");
        // 打开一个文件通道
        FileChannel channel = accessFile.getChannel();
        // 初始化一个 容量为 48 bytes 的 buffer 缓冲
        ByteBuffer buf = ByteBuffer.allocate(48);
        buf.clear();
        String newData = "New String to write to file..." + System.currentTimeMillis();

        buf.put(newData.getBytes());
        buf.flip();
        // 注意FileChannel.write()是在while循环中调用的。
        // 因为无法保证write()方法一次能向FileChannel写入多少字节，
        // 因此需要重复调用write()方法，直到Buffer中已经没有尚未写入通道的字节。
        while (buf.hasRemaining()) {
            channel.write(buf);
        }
        // FileChannel.force()方法将通道里尚未写入磁盘的数据强制写到磁盘上。出于性能方面的考虑，
        // 操作系统会将数据缓存在内存中，所以无法保证写入到FileChannel里的数据一定会即时写到磁盘上。
        // 要保证这一点，需要调用force()方法。
        // 该方法会使操作系统调用 fsync 接口来进行缓存写入磁盘
        channel.force(true);
        channel.close();
        // 关闭通道
        accessFile.close();
    }

}

/**
 * 有时可能需要在FileChannel的某个特定位置进行数据的读/写操作。
 * 可以通过调用position()方法获取FileChannel的当前位置。
 * 也可以通过调用position(long pos)方法设置FileChannel的当前位置。
 */
class TestPosition {
    public static void main(String[] args) throws Exception {
        RandomAccessFile accessFile = new RandomAccessFile("src\\maindemo\\resources\\file\\nio\\nio-data.txt", "rw");
        // 打开一个文件通道
        FileChannel channel = accessFile.getChannel();
        // FileChannel实例的size()方法将返回该实例所关联文件的大小
        System.out.println("channelSize:" + channel.size());
        // 可以使用FileChannel.truncate()方法截取一个文件。截取文件时，文件将中指定长度后面的部分将被删除
        // 该操作会立即直接作用到文件中无需等待通道关闭等操作
        channel.truncate(8);
        System.out.println("channelSize:" + channel.size());
        // 如果将位置设置在文件结束符之后，然后试图从文件通道中读取数据，读方法将返回-1 —— 文件结束标志
        long pos = channel.position();
        System.out.println("position " + pos);
        // pos + 2 跳过两个指针内容
        // 如果将位置设置在文件结束符之后，然后向通道中写数据，文件将撑大到当前位置并写入数据。
        // 这可能导致“文件空洞”，磁盘上物理文件中写入的数据间有空隙。
        channel.position(pos + 2);
        ByteBuffer buffer = ByteBuffer.allocate(48);
        int count = channel.read(buffer);
        while (count != -1) {
            buffer.flip();
            while (buffer.hasRemaining()) {
                System.out.println(buffer.get());
            }
            buffer.clear();
            count = channel.read(buffer);
        }
        channel.close();
        accessFile.close();
    }
}
